#ifndef BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_TEST_BACKEND_PROTOBUF_H_
#define BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_TEST_BACKEND_PROTOBUF_H_

#include <cstddef>
#include <optional>
#include <string>
#include <vector>

#include <inja/inja.hpp>

#include "backends/p4tools/common/control_plane/p4info_map.h"
#include "control-plane/p4RuntimeSerializer.h"
#include "ir/ir.h"
#include "lib/cstring.h"

#include "backends/p4tools/modules/testgen/lib/test_spec.h"
#include "backends/p4tools/modules/testgen/targets/bmv2/test_backend/common.h"

namespace P4::P4Tools::P4Testgen::Bmv2 {

using P4::ControlPlaneAPI::p4rt_id_t;

struct ProtobufTest : public AbstractTest {
 private:
    /// The formatted test. TODO: This should be a Protobuf object.
    std::string formattedTest_;

 public:
    explicit ProtobufTest(std::string formattedTest) : formattedTest_(std::move(formattedTest)) {}

    /// @return the formatted test.
    [[nodiscard]] const std::string &getFormattedTest() const { return formattedTest_; }

    DECLARE_TYPEINFO(ProtobufTest);
};

/// Extracts information from the @testSpec to emit a Protobuf test case.
class Protobuf : public Bmv2TestFramework {
 public:
    explicit Protobuf(const TestBackendConfiguration &testBackendConfiguration,
                      P4::P4RuntimeAPI p4RuntimeApi);

    void writeTestToFile(const TestSpec *testSpec, cstring selectedBranches, size_t testId,
                         float currentCoverage) override;

    AbstractTestReferenceOrError produceTest(const TestSpec *testSpec, cstring selectedBranches,
                                             size_t testIdx, float currentCoverage) override;

 private:
    /// The P4Runtime API generated by the compiler. This API can be used to look up the id of
    /// tables.
    P4::P4RuntimeAPI p4RuntimeApi;

    /// A mapping from P4 control plane names to their mapped P4Runtime ids and vice versa.
    P4::ControlPlaneAPI::P4InfoMaps p4InfoMaps;

    inja::json getControlPlane(const TestSpec *testSpec) const override;

    [[nodiscard]] inja::json getSend(const TestSpec *testSpec) const override;

    [[nodiscard]] inja::json getExpectedPacket(const TestSpec *testSpec) const override;

    /// Wrapper helper function that automatically inserts separators for hex strings.
    static std::string formatHexExprWithSep(const IR::Expression *expr);

    /// Looks up the P4Runtime id for the given control plane name in the pre-computed P4Runtime-ID
    /// map. @returns std::nullopt if the name is not in the map.
    [[nodiscard]] std::optional<p4rt_id_t> lookupP4RuntimeId(cstring controlPlaneName) const;

    /// Emits the test preamble. This is only done once for all generated tests.
    /// For the protobuf back end this is the "p4testgen.proto" file.
    void emitPreamble(const std::string &preamble);

    /// Generates a test case.
    /// @param selectedBranches enumerates the choices the interpreter made for this path.
    /// @param testId specifies the test name.
    /// @param currentCoverage contains statistics  about the current coverage of this test and its
    /// preceding tests.
    inja::json produceTestCase(const TestSpec *testSpec, cstring selectedBranches, size_t testId,
                               float currentCoverage) const;

    /// @returns the inja test case template as a string.
    static std::string getTestCaseTemplate();

    /// The Protobuf back end needs the parent table and action name to correctly identify the
    /// corresponding P4Runtme id. This is why we use a custom "getControlPlaneForTable" function
    /// here.
    [[nodiscard]] inja::json getControlPlaneForTable(cstring tableName, cstring actionName,
                                                     const TableMatchMap &matches,
                                                     const std::vector<ActionArg> &args) const;
};

}  // namespace P4::P4Tools::P4Testgen::Bmv2

#endif /* BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_TEST_BACKEND_PROTOBUF_H_ */
